package cn.bbstone.pisces2.client.util;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.bbstone.pisces2.comm.BFileCmd;
import cn.bbstone.pisces2.comm.Const;
import cn.bbstone.pisces2.comm.cache.ClientFliIndexCache;
import cn.bbstone.pisces2.comm.fli.FliIndex;
import cn.bbstone.pisces2.proto.rsp.RspFile;
import cn.bbstone.pisces2.util.BByteUtil;
import cn.bbstone.pisces2.util.BFileUtil;
import cn.hutool.core.util.IdUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;

/**
 * build client request msg & server response ByteBuf msg
 */
public class MsgUtil {
    private static Logger log = LoggerFactory.getLogger(MsgUtil.class);

    private static volatile boolean initialedRead = false;

    /**
     * to control multi client only one client to read file list index
     * and build .fli_client/fli.idx file
     *
     */
    private static volatile boolean reqListInfoStarted = false;
    private static volatile boolean rspListDataCompleted = false;


    public static void resetInitialStatus() {
    	initialedRead = false;
    	reqListInfoStarted = false;
    	rspListDataCompleted = false;
    }

    /**
     * the very first request after client started for the fli.idx file info
     * request sequence:
     *                                                +--LOOP------------------------------------------+
     *  1) fli.idx fileInfo -> 2) fli.idx fileData -> | 3)fileNo = x fileInfo -> 4)fileNo = x fileData |
     *                                                +------------------------------------------------+
     *
     * @param ctx
     */
    public synchronized static void firstReqListInfo(ChannelHandlerContext ctx) {
        // send REQ_LIST_INFO request, then receive File List Index data
        // to build/(skip update) client .fli_client/fli.idx file and .fli_client/fli.sp
        // fileNo = 0, means fli.idx file
        if (!reqListInfoStarted) {
            ctx.writeAndFlush(MsgUtil.buildReq(BFileCmd.REQ_LIST_INFO, 0L));
            reqListInfoStarted = true;
            return; // the first client first run should not block for accept rsp
        }
        // wait till rspListDataCompleted
        while (!rspListDataCompleted) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.error("thread sleep error.", e);
            }
        }
        if (rspListDataCompleted) {
            // there is anyone client has req fli.idx file complete, so current client direct reqest fileNo = x in fli.idx
            reqNextFileInfo(ctx);
        }
    }

    /**
     * this method should only be invoke by one client
     */
    public static void rspListDataCompleted() {
        rspListDataCompleted = true;
    }

//    public synchronized static FliIndex findIndex(Long fileNo) {
//        FliIndex fliIndex = null;
//        if (fileNo == null)
//            fliIndex = ClientFliIndexCache.next();
//        else
//            fliIndex = ClientFliIndexCache.getClientIndex(fileNo);
//        return fliIndex;
//    }

    /**
     * After RSP_LIST_DATA/RSP_FILE_DATA all chunks received, will trigger request next file(fileInfo)
     *
     * multi client will invoke this method,  need be synchronized to access ClientFliIndexCache
     *
     * LOOP step 3)
     *
     * @param ctx
     */
    public synchronized static void reqNextFileInfo(ChannelHandlerContext ctx) {
        FliIndex fliIndex = null;
        if ( initialedRead ) {
            fliIndex = ClientFliIndexCache.next();
            reqNextFileInfo(ctx, fliIndex);
        } else { // first time run here after client started
        	
        	// when run as multiple client instance mode, the fileNo in ~/.fli_client/fli.sp has no sense.
//            FliSavePoint fliSavePoint = FLIUtil.readClientSavePoint();
//            if (fliSavePoint.getFileNo() > 0) { // start from fileNo = sp.fileNo
//                log.debug("-->-->--> start req file from save-point, fileNo: {}", fliSavePoint.getFileNo());
//                fliIndex = ClientFliIndexCache.getClientIndex(fliSavePoint.getFileNo());
//                reqNextFileInfo(ctx, fliIndex);
//            } else { // start from fileNo = 1
                fliIndex = ClientFliIndexCache.next();
                reqNextFileInfo(ctx, fliIndex);
//            }
            initialedRead = true;
        }
        if (fliIndex != null) 
        	log.debug("<><><> reqNextFileInfo.fileNo: {}, rpath: {}", fliIndex.getFileNo(), fliIndex.getRpath());
    }


    /**
     * next fileNo get from FliIndexCache(of fli.idex file)
     *
     * @param ctx
     */
//    private static void reqNextFileInfo(ChannelHandlerContext ctx, long fileNo) {
//        FliIndex fliIndex = ClientFliIndexCache.getClientIndex(fileNo);
//        reqNextFileInfo(ctx, fliIndex);
//    }

    private static void reqNextFileInfo(ChannelHandlerContext ctx, FliIndex fliIndex) {
        if (fliIndex == null || fliIndex.getFileNo() == -1L) {
            log.info("<*>end of fli.idx file, all file process.<*>");
            // TODO close FileClient
//            ctx.channel().close();
//            ClientStarter.shutdown(); // do not shutdown client auto, but by manual
            return;
        }
        // directly create fli.idx directory for client, will not update fli.sp file
        while (!Const.BFILE_CAT_FILE.equals(fliIndex.getFileCat())) { // D,F,SL, is there any other fileCat?
            if (Const.BFILE_CAT_DIR.equals(fliIndex.getFileCat())) {
                BFileUtil.mkdir(BFileUtil.getClientFullPath(fliIndex.getRpath4Client(), true));
            } // else if Symbolic Link, skip to next ---
            if (Const.BFILE_CAT_SL.equals(fliIndex.getFileCat())) {
                log.debug("fliIdex(rpath): {} is Symbolic Link, will skip...", fliIndex.getRpath());
            }
            fliIndex = ClientFliIndexCache.next();
            if (fliIndex.getFileNo() == -1L) {
                log.info("<*>end of fli.idx file, all file process.<*>");
                return;
            }
        }
        // TODO check if rpath file exist in client, if so, skip req next file

        // index Item is F(file), send REQ_FILE_INFO to download the file from server
        if (Const.BFILE_CAT_FILE.equals(fliIndex.getFileCat())) {
            ctx.writeAndFlush(MsgUtil.buildReq(BFileCmd.REQ_FILE_INFO, fliIndex.getFileNo()));
        }
        log.debug(">>>>>>>>>>>>>>> client request next file(fileNo: {}) . <<<<<<<<<<<<<<<<<", fliIndex.getFileNo());
    }

    // ---------- build req msg
    public static ByteBuf buildReq(Byte msgType, long fileNo) {
        return buildReq(msgType, fileNo, (short) 0, new byte[0]);
    }

    public static ByteBuf buildReq(Byte msgType, long fileNo, short bodyLen, byte[] bodyData) {
        ByteBuf buf = Unpooled.buffer();
        // magic
        buf.writeBytes(BByteUtil.toBytes(Const.magic));
        // msgType
        buf.writeByte(msgType); // 1B: msgType
        // msgId
        buf.writeBytes(BByteUtil.toBytes(IdUtil.fastSimpleUUID())); // 32B: uuid
        // reqTs
        buf.writeLong(System.currentTimeMillis()); // 8B
        // fileNo
        buf.writeLong(fileNo);
        // body length
        buf.writeShort(bodyLen); // 2B: msg body Len (0-nobody)
        // bodyData
        buf.writeBytes(bodyData);// empty body
        log.debug("{} req ({}) byte[]: {}, ", msgType, buf.readableBytes(), buf.array());
        return buf;
    }

    public static ByteBuf buildRsp(RspFile rspFile) {
        ByteBuf buf = Unpooled.buffer();
        // magic
        buf.writeBytes(BByteUtil.toBytes(Const.magic));
        // msgType
        buf.writeByte(rspFile.getMsgType());
        // msgId
        buf.writeBytes(BByteUtil.toBytes(rspFile.getMsgId()));
        // reqTs
        buf.writeLong(rspFile.getReqTs());
        // rspTs
        buf.writeLong(rspFile.getRspTs());
        // fileNo
        buf.writeLong(rspFile.getFileNo());
        // chunkNo
        buf.writeInt(rspFile.getChunkNo());
        // chunks
        buf.writeInt(rspFile.getChunks());
        // body checksum
        if (rspFile.getChecksum() == null) { // empty file(no data)
            buf.writeBytes(new byte[32]);
        } else {
            buf.writeBytes(BByteUtil.toBytes(rspFile.getChecksum())); // bodyData's checksum hex to byte[]
        }
        // body length
        buf.writeInt(rspFile.getBodyLen());
        // body data (when transfer fli.idx file(msgType=0x03), body data is this file's checksum), other
        buf.writeBytes(rspFile.getBodyData()); // bodyData is
//        log.info("msgType: {} req ({}) byte[]: {}, ", rspFile.getMsgType(), buf.readableBytes(), buf.array());
        return buf;
    }



    public static void main(String[] args) {
//        String uuid = IdUtil.fastSimpleUUID();
//        byte[] uuidBytes = uuid.getBytes(StandardCharsets.UTF_8);
//        System.out.println(uuidBytes.length);
//        System.out.println("fli.idx bodyLen: " + BByteUtil.toBytes(FLIUtil.FLI_IDX).length);
//        reqIdx();


//        log.info("string.md5: {}", CipherUtil.md5("hello,world!"));
//        log.info("file.md5: {}", BFileUtil.checksum(new File("/Users/bbstone/.fli_server/fli.idx")));
//        byte[] bodyBytes = BByteUtil.toBytes(FLIUtil.FLI_IDX);
//        byte[] bytes = BByteUtil.toBytes(CipherUtil.md5(bodyBytes));
//        log.info("bytes.len: {}", bytes.length);
//        // fli.idx
////        reqIdx();
//        log.info("magic.len: {}", BByteUtil.toBytes(Const.magic).length);


        ClientFliIndexCache.init(Const.CLIENT);
//        String rpath = ClientFliIndexCache.getClientIndex(946).getRpath();
//        log.info("rpath: {}", rpath);

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> {
                reqNextFileInfo(null);
//                    FliIndex fliIndex = ClientFliIndexCache.next();
//                    log.info("<><><> reqNextFileInfo.fileNo: {}, rpath: {}", fliIndex.getFileNo(), fliIndex.getRpath());
            });
        }



    }

}
